﻿//-----------------------------------------------------------------------
// <copyright file="PlayerService.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings
using System;
using GateShift.Common.DataPackets;
using GateShift.Common.DataPackets.Player;
using GateShift.Services.Common;
using GateShift.Services.SDK.Services;
using GateShift.Services.SDK.Services.Events;
using GateShift.Services.SDK.Services.HostIntegration;
using GateShift.Services.SDK.Threading;
using Microsoft.Xna.Framework;
#endregion

namespace GateShift.Services.PlayerSystem
{
    /// <summary>
    /// General service for handling players in the GateShift System
    /// </summary>
    [LoadableService(typeof(IPlayerService))]
    public class PlayerService : ServiceBase, IPlayerService, IClientDataHandler
    {
        private readonly ThreadedDictionary<Guid, PlayerData> _playerStates;
        private int _currentPlayerID = 0;

        /// <summary>
        /// Initializes a new instance of the PlayerService class
        /// </summary>
        public PlayerService()
        {
            _playerStates = new ThreadedDictionary<Guid, PlayerData>();
        }

        /// <summary>
        /// Method to initilaize the service. 
        /// </summary>
        public override void Initialize()
        {
            ServerHost.SessionHost.SessionConnected += SessionHost_SessionConnected;
            ServerHost.SessionHost.SessionDisconnected += SessionHost_SessionDisconnected;
        }

        #region IClientDataHandler Members

        /// <summary>
        /// Method called to handle data from the client
        /// </summary>
        /// <param name="session">IClientSession sending the data</param>
        /// <param name="serviceCode">Service Code to handle</param>
        /// <param name="data">Data to deserialize into an IDataPacket</param>
        /// <param name="wasEncrypted">Whether or not the packet was encrypted</param>
        public void HandleClientData(IClientSession session, byte serviceCode, byte[] data, bool wasEncrypted)
        {
            switch ((ServerPlayerServiceCode)serviceCode)
            {
                case ServerPlayerServiceCode.PlayerPosition:
                    HandlePlayerPosition(session, PacketConverter.ConvertFromByteArray<ServerPlayerPositionPacket>(data));
                    break;
            }
        }

        #endregion

        #region Session Events

        /// <summary>
        /// Handles players disconnecting
        /// </summary>
        /// <param name="sender">Object calling this method</param>
        /// <param name="e">ClientSessionEventArgs about the event</param>
        private void SessionHost_SessionDisconnected(object sender, ClientSessionEventArgs e)
        {
            ServerHost.SessionHost.SendClientData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerDisconnection, PacketConverter.ConvertToByteArray(new ClientPlayerRemovePacket() { PlayerID = _playerStates[e.Session.SessionID].PlayerID }), false);

            _playerStates.Remove(e.Session.SessionID);
        }

        /// <summary>
        /// Handles players connecting
        /// </summary>
        /// <param name="sender">Object calling this method</param>
        /// <param name="e">ClientSessionEventArgs about the event</param>
        private void SessionHost_SessionConnected(object sender, ClientSessionEventArgs e)
        {
            _currentPlayerID++;

            _playerStates.Add(e.Session.SessionID, new PlayerData() { PlayerID = _currentPlayerID, Position = Vector4.Zero, State = 0 });

            ServerHost.SessionHost.SendClientData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerPosition, PacketConverter.ConvertToByteArray(new ClientPlayerDataPacket() { AxisX = 0, AxisY = 0, AxisZ = 0, Rotation = 0, PlayerID = _currentPlayerID }), false);

            ServerHost.SessionHost.SendClientData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerStateChange, PacketConverter.ConvertToByteArray(new ClientPlayerStateChangePacket() { PlayerID = _currentPlayerID, NewState = 0 }), false);

            foreach (PlayerData pair in _playerStates.Values)
            {
                e.Session.SendData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerPosition, PacketConverter.ConvertToByteArray(new ClientPlayerDataPacket() { AxisX = pair.Position.X, AxisY = pair.Position.Y, AxisZ = pair.Position.Z, Rotation = pair.Position.W, PlayerID = pair.PlayerID }), false);

                e.Session.SendData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerPosition, PacketConverter.ConvertToByteArray(new ClientPlayerStateChangePacket() { PlayerID = pair.PlayerID, NewState = pair.State }), false);
            }
        }

        #endregion

        /// <summary>
        /// Handles commands for player positioning
        /// </summary>
        /// <param name="session">IClientSession with the positional data</param>
        /// <param name="packet">ServerPlayerPositionPacket about the new position</param>
        private void HandlePlayerPosition(IClientSession session, ServerPlayerPositionPacket packet)
        {
            if (_playerStates == null)
            {
                return;
            }

            PlayerData data = _playerStates[session.SessionID];

            data.Position.X = packet.AxisX;
            data.Position.Y = packet.AxisY;
            data.Position.Z = packet.AxisZ;
            data.Position.W = packet.Rotation;

            _playerStates[session.SessionID] = data;

            ClientPlayerDataPacket newPacket = new ClientPlayerDataPacket { AxisX = packet.AxisX, AxisY = packet.AxisY, AxisZ = packet.AxisZ, Rotation = packet.Rotation, PlayerID = data.PlayerID };

            ServerHost.SessionHost.SendClientData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerPosition, PacketConverter.ConvertToByteArray(newPacket), false);
        }

        /// <summary>
        /// Handles commands for player state changes
        /// </summary>
        /// <param name="session">IClientSession with the state data</param>
        /// <param name="packet">ServerPlayerPositionPacket about the new state</param>
        private void HandlePlayerState(IClientSession session, ServerPlayerStateChangePacket packet)
        {
            if (_playerStates == null)
            {
                return;
            }

            PlayerData data = _playerStates[session.SessionID];

            data.State = packet.NewState;

            _playerStates[session.SessionID] = data;

            ClientPlayerStateChangePacket newPacket = new ClientPlayerStateChangePacket
                                                          {
                                                              PlayerID = data.PlayerID,
                                                              NewState = data.State
                                                          };

            ServerHost.SessionHost.SendClientData((byte)ClientServiceID.PlayerService, (byte)ClientPlayerServiceCode.PlayerStateChange, PacketConverter.ConvertToByteArray(newPacket), false);
        }

        /// <summary>
        /// Basic struct for managing player state and position
        /// </summary>
        private struct PlayerData
        {
            public Vector4 Position;
            public int PlayerID;
            public short State;
        }
    }
}